package model.os;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.SwingUtilities;

import model.Observable;
import model.os.pagereplacement.FifoW2Chance;
import model.os.pagereplacement.LRU;
import model.os.pagereplacement.PageReplacementStrategy;
import model.system.HardDisk;
import model.system.RAM;
import view.util.OSProcessColor;
import controller.Application;
import controller.Main;

public class OS extends Observable {

	private static final OS INSTANCE;

	public static final int PAGE_SIZE = 10;

	private Map<OSProcess, PageTable> pageTables;

	private HardDisk hardDisk;

	private RAM ram;

	private Scheduler scheduler;

	private PageReplacementStrategy pageReplacementStrategy;

	static {
		INSTANCE = new OS();
	}

	private OS() {
		pageTables = new HashMap<OSProcess, PageTable>();
		hardDisk = new HardDisk();
		ram = new RAM();
		scheduler = new Scheduler();
		pageReplacementStrategy = new FifoW2Chance();
	}

	public void createProcesses() {
		OSProcessColor[] colors = OSProcessColor.values();
		OSProcess process;
		for (int i = 0; i < Main.PROCESSES; i++) {
			process = new OSProcess(i);
			process.setColor(colors[i].getColor());
			hardDisk.store(process);
			createProcessTable(process);
			scheduler.add(process);
		}
	}

	private void createProcessTable(OSProcess process) {
		PageTable table = new PageTable();
		for (int i = 0; i < process.getSize(); i++) {
			table.getEntries().put(i, new PageTableEntry());
		}
		pageTables.put(process, table);
	}

	public void start() {
		scheduler.runAllProcesses();
	}

	public Page getPage(OSProcess process, Address virtualAddress) {
		PageTable table = pageTables.get(process);
		Address physicalAddress = table.getPhysicalAdress(virtualAddress);
		PageTableEntry entry = table.getEntries().get(
				virtualAddress.getNumber());
		int location = 0;
		if (physicalAddress == null) {
			Page page = hardDisk.getPage(virtualAddress.getNumber(), process);
			if (ram.isFull()) {
				location = pageReplacementStrategy.getPageToReplace();
				ram.add(page, location);
			} else {
				location = ram.add(page);
			}
			table.updateEntry(page.getNumber(), location);
			pageReplacementStrategy.addPage(entry);
		} else {
			location = physicalAddress.getNumber();
			pageReplacementStrategy.refferenced(entry);
		}
		notifyObserversSecurly();
		return ram.getFrame(location);
	}

	public void setPageReplacementStrategy(String algorithm) {
		if (algorithm.equalsIgnoreCase("LRU")) {
			setPageReplacementStrategy(new LRU());

			// notifyObserversSecure();
		} else if (algorithm.equalsIgnoreCase("FIFOW2C")) {
			setPageReplacementStrategy(new FifoW2Chance());

			// notifyObserversSecure();
		}
		restart();
	}

	public void notifyObserversSecurly() {

		Runnable notifier = new Runnable() {
			public void run() {
				notifyObservers();
			}
		};

		try {
			SwingUtilities.invokeAndWait(notifier);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

		try {
			Thread.sleep(Main.PROCESS_SPEED);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}

	}

	private void restart() {
		ram = new RAM();
		scheduler.stopAll();

		notifyObservers();
	}

	public Map<OSProcess, PageTable> getPageTables() {
		return pageTables;
	}

	public PageTable getPageTable(OSProcess process) {
		return pageTables.get(process);
	}

	public void setPageTables(Map<OSProcess, PageTable> pageTable) {
		this.pageTables = pageTable;
	}

	public static OS getInstance() {
		return INSTANCE;
	}

	public void setRam(RAM ram) {
		this.ram = ram;
	}

	public RAM getRam() {
		return ram;
	}

	public HardDisk getHardDisk() {
		return hardDisk;
	}

	public void setHardDisk(HardDisk hardDisk) {
		this.hardDisk = hardDisk;
	}

	public PageReplacementStrategy getPageReplacementStrategy() {
		return pageReplacementStrategy;
	}

	public void setPageReplacementStrategy(
			PageReplacementStrategy pageReplacementStrategy) {
		this.pageReplacementStrategy = pageReplacementStrategy;
	}

	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	public Scheduler getScheduler() {
		return scheduler;
	}

}
